package com.basin.common.redis.util;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * @author: Sam ZHONG | sammy1997@aliyun.com
 */
@Slf4j
public final class LockUtil {
    private LockUtil() {
    }

    private static final long TIME = 30000;
    private static final long SLEEP_TIME = 500;

    private static final TimeUnit UNIT = TimeUnit.MILLISECONDS;

    private static final RedissonClient CLIENT = RedisUtil.getClient();

    public static RLock getLock(String key) {
        return CLIENT.getLock(key);
    }

    public static <T> T executeWithDistributedLock(Supplier<T> supplier, String key) {
        return invoke(supplier, key, TIME, UNIT);
    }

    public static <T> T executeWithDistributedLock(Supplier<T> supplier, String key, long timeout, TimeUnit unit) {
        return invoke(supplier, key, timeout, unit);
    }

    public static <T> T executeWithDistributedLock(Supplier<T> supplier, String key, long timeout, TimeUnit unit, int retries) {
        int count = retries;
        try {
            return invoke(supplier, key, timeout, unit);
        } catch (Exception e) {
            if (count > 0) {
                try {
                    TimeUnit.MILLISECONDS.sleep(SLEEP_TIME);
                    return executeWithDistributedLock(supplier, key, timeout, unit, --count);
                } catch (InterruptedException ex) {
                    throw new RuntimeException("重试时出现异常", ex);
                }
            } else {
                throw new RuntimeException("重试指定次数，获取锁仍然失败");
            }
        }
    }

    private static <T> T invoke(Supplier<T> supplier, String key, long time, TimeUnit unit) {
        RLock lock = getLock(key);
        boolean success;
        Exception ex = null;
        try {
            success = lock.tryLock(time, unit);
        } catch (Exception e) {
            ex = e;
            success = false;
        }

        if (success) {
            try {
                return supplier.get();
            } catch (Exception e) {
                log.error("分布式锁执行业务异常", e);
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        } else {
            // 别自旋到爆炸了 此工具提供重试机制
            throw new RuntimeException("获取锁失败", ex);
        }
    }
}
